|
1
|
|
|
'use strict'; |
|
2
|
|
|
|
|
3
|
|
|
var objectAssign = require( 'object-assign' ), |
|
4
|
|
|
_ = require( 'underscore' ), |
|
5
|
|
|
|
|
6
|
|
|
ElementStart = { |
|
7
|
|
|
MARGIN: 'MARGIN', |
|
8
|
|
|
ELEMENT: 'ELEMENT', |
|
9
|
|
|
PADDDING: 'PADDING' |
|
10
|
|
|
}, |
|
11
|
|
|
|
|
12
|
|
|
calculateFixedHeaderElementHeight = function ( $fixedHeaderElements ) { |
|
13
|
|
|
return _.reduce( $fixedHeaderElements.get(), function ( offset, element ) { |
|
14
|
|
|
var $elm = $( element ); |
|
15
|
|
|
if ( $elm.is( ':visible' ) ) { |
|
16
|
|
|
offset += $elm.height(); |
|
17
|
|
|
} |
|
18
|
|
|
return offset; |
|
19
|
|
|
}, 0 ); |
|
20
|
|
|
}, |
|
21
|
|
|
|
|
22
|
|
|
calculateElementPadding = function ( $element ) { |
|
23
|
|
|
var matchedElemPadding = $element.css( 'padding-top' ).match( /^(\d+)px$/ ); |
|
24
|
|
|
|
|
25
|
|
|
if ( !matchedElemPadding ) { |
|
26
|
|
|
return 0; |
|
27
|
|
|
} |
|
28
|
|
|
return parseInt( matchedElemPadding[ 1 ] ); |
|
29
|
|
|
}, |
|
30
|
|
|
|
|
31
|
|
|
calculateElementMargin = function ( $element ) { |
|
32
|
|
|
var matchedElemPadding = $element.css( 'margin-top' ).match( /^(\d+)px$/ ); |
|
33
|
|
|
|
|
34
|
|
|
if ( !matchedElemPadding ) { |
|
35
|
|
|
return 0; |
|
36
|
|
|
} |
|
37
|
|
|
return parseInt( matchedElemPadding[ 1 ] ); |
|
38
|
|
|
}, |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* |
|
42
|
|
|
* @param {jQuery} $element Element whose offset will be taken |
|
43
|
|
|
* @param {[jQuery]} $fixedHeaderElements Elements whose height will be subtracted from the offset |
|
44
|
|
|
* @param {Object} options |
|
45
|
|
|
* @param {string} options.elementStart |
|
46
|
|
|
* @return {number} |
|
47
|
|
|
*/ |
|
48
|
|
|
calculateElementOffset = function ( $element, $fixedHeaderElements, options ) { |
|
49
|
|
|
options = _.extend( { elementStart: ElementStart.ELEMENT }, options ); |
|
50
|
|
|
var offset = $element.offset().top - calculateFixedHeaderElementHeight( $fixedHeaderElements ); |
|
51
|
|
|
switch ( options.elementStart ) { |
|
|
|
|
|
|
52
|
|
|
case ElementStart.PADDDING: |
|
|
|
|
|
|
53
|
|
|
return offset + calculateElementPadding( $element ); |
|
54
|
|
|
case ElementStart.MARGIN: |
|
55
|
|
|
return offset - calculateElementMargin( $element); |
|
56
|
|
|
} |
|
57
|
|
|
return offset; |
|
58
|
|
|
}, |
|
59
|
|
|
|
|
60
|
|
|
AnimatedScroller = { |
|
61
|
|
|
fixedHeaderElements: null, |
|
62
|
|
|
scrollTo: function( $element, options ) { |
|
63
|
|
|
$( 'html, body' ).stop( true ).animate( { |
|
64
|
|
|
scrollTop: calculateElementOffset( $element, this.fixedHeaderElements, options ) |
|
65
|
|
|
}, 1000, function () { |
|
66
|
|
|
// Callback after animation |
|
67
|
|
|
// Must change focus! |
|
68
|
|
|
$element.focus(); |
|
69
|
|
|
if ($element.is( ':focus' ) ) { // Checking if the target was focused |
|
70
|
|
|
return false; |
|
71
|
|
|
} else { |
|
72
|
|
|
$element.attr( 'tabindex', '-1' ); // Adding tabindex for elements not focusable |
|
73
|
|
|
$element.focus(); // Set focus again |
|
|
|
|
|
|
74
|
|
|
} |
|
75
|
|
|
} ); |
|
76
|
|
|
|
|
77
|
|
|
} |
|
78
|
|
|
}, |
|
79
|
|
|
|
|
80
|
|
|
LinkScroller = { |
|
81
|
|
|
scroller: null, |
|
82
|
|
|
linkIsInsideCompletedSummaryOnSmallScreen: function( link ) { |
|
83
|
|
|
// only the completed fields at the bottom summary are inside a .wrap-field.completed |
|
84
|
|
|
return $( window ).width() < 1200 && $( link ).closest( '.wrap-field.has-longtext.completed .wrap-input' ).length > 0; |
|
85
|
|
|
}, |
|
86
|
|
|
scrollToTarget: function( evt ) { |
|
87
|
|
|
evt.preventDefault(); |
|
88
|
|
|
|
|
89
|
|
|
if ( this.linkIsInsideCompletedSummaryOnSmallScreen( evt.currentTarget ) ) { |
|
90
|
|
|
return; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
var target = $( evt.currentTarget.hash ); |
|
94
|
|
|
target = target.length ? target : $( '[name=' + evt.currentTarget.hash.slice( 1 ) + ']' ); |
|
95
|
|
|
if ( target.length > 0 ) { |
|
96
|
|
|
this.scroller.scrollTo( target, { elementStart: ElementStart.PADDDING } ); |
|
97
|
|
|
} |
|
98
|
|
|
} |
|
99
|
|
|
} |
|
100
|
|
|
; |
|
101
|
|
|
|
|
102
|
|
|
module.exports ={ |
|
103
|
|
|
createAnimatedScroller: function ( fixedHeaderElements ) { |
|
104
|
|
|
return objectAssign( Object.create( AnimatedScroller ), { fixedHeaderElements: fixedHeaderElements } ); |
|
105
|
|
|
}, |
|
106
|
|
|
scrollOnSuboptionChange: function( $suboptionInput, $suboptionContainer, scroller ) { |
|
107
|
|
|
$suboptionInput.on( 'change', function ( evt ) { |
|
108
|
|
|
var wrapper = $suboptionContainer.find( '.wrap-field input[value=' + evt.target.value + ']' ).parents( '.wrap-field' ).find( '.info-text' ); |
|
109
|
|
|
if (wrapper.length) { |
|
110
|
|
|
scroller.scrollTo( wrapper, { elementStart: ElementStart.ELEMENT } ); |
|
111
|
|
|
} |
|
112
|
|
|
} ) |
|
113
|
|
|
}, |
|
114
|
|
|
/** |
|
115
|
|
|
* Ensure smooth scroll to the given anchor links. Make sure to only pass links on the same page that can be scrolled to. |
|
116
|
|
|
* |
|
117
|
|
|
* @param {jQuery} $links |
|
118
|
|
|
* @param {object} scroller |
|
119
|
|
|
*/ |
|
120
|
|
|
addScrollToLinkAnchors: function( $links, scroller ) { |
|
121
|
|
|
var linkScroller = objectAssign( Object.create( LinkScroller ), { scroller: scroller } ); |
|
122
|
|
|
$links.not('[href="#"]') |
|
123
|
|
|
.not('[href="#0"]') |
|
124
|
|
|
.not('.state-overview .wrap-field.completed .wrap-input') |
|
125
|
|
|
.click( linkScroller.scrollToTarget.bind( linkScroller ) ); |
|
126
|
|
|
}, |
|
127
|
|
|
ElementStart: ElementStart, |
|
128
|
|
|
// exposed for testing |
|
129
|
|
|
calculateElementOffset: calculateElementOffset |
|
130
|
|
|
}; |
|
131
|
|
|
|